//
//  ViewController.m
//  client_with_socket
//
//  Created by flynn on 2024/3/4.
//

//。 本地启动代理可以使用 gost命令，来自于brew install gost。具体启动命令可以 gost -L:=8000 本地启动代理监听8000端口，测试的话可以使用firefox，配置代理127.0.0.1:8000，然后访问baidu.com即可看到代理在运行

#import "ViewController.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#import <CFNetwork/CFHTTPStream.h>

#include <JavaScriptCore/JavaScriptCore.h>



#include <openssl/ssl.h>
#include <openssl/err.h>

@interface ViewController()<NSURLSessionDelegate,NSURLSessionTaskDelegate>
@end
/*
 OpenSSL 中的 BIO（Basic I/O）是一种抽象的 I/O 对象，它提供了统一的接口来处理各种类型的数据输入输出。BIO 可以用于处理文件、套接字、内存等不同的数据源和目的地，同时还可以在 OpenSSL 中用于实现 SSL/TLS 连接。

 使用 OpenSSL 的 BIO 接口来访问 HTTPS 和直接使用 SSL_xx 接口实现访问 HTTPS 有一些区别：

 灵活性：

 使用 SSL_xx 接口需要手动管理底层的 TCP 连接，并且需要处理 SSL/TLS 握手等细节，因此对于开发者来说需要编写更多的代码来完成整个 HTTPS 请求过程。相比之下，使用 BIO 接口可以更方便地处理各种不同类型的 I/O，例如文件、套接字等，使得代码更加灵活并且可重用性更高。
 封装性：

 使用 SSL_xx 接口时，需要手动处理 SSL/TLS 握手、加密解密等细节，包括错误处理、证书验证等。而使用 BIO 接口时，这些细节已经被封装在底层的 BIO 对象中，使得编程接口更加简洁，并且能够隐藏底层的实现细节。
 总的来说，使用 OpenSSL 的 BIO 接口能够提供更高层次的抽象和封装，使得开发者在使用 SSL/TLS 连接时能够更加方便、灵活地处理各种 I/O 操作，同时也能够降低编程复杂度。
 */
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}
#define PROXY_ADDRESS "127.0.0.1"
#define PROXY_PORT 8000
#define TARGET_ADDRESS "172.18.100.212"
#define TARGET_PORT 6789

/*
 BSD socket+opensslBIO接口访问HTPPS
 */
- (int) requestHTTPSWithBIO {
    // 初始化 OpenSSL 库
    SSL_library_init();
    ERR_load_BIO_strings();
    SSL_load_error_strings();

    // 创建 TCP 连接
    struct addrinfo hints, *result;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    getaddrinfo("www.baidu.com", "443", &hints, &result);

    // 创建socket连接
    int sockfd = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (sockfd == -1) {
        fprintf(stderr, "Failed to create socket\n");
        return 1;
    }
    if (connect(sockfd, result->ai_addr, result->ai_addrlen) == -1) {
        fprintf(stderr, "Failed to connect to server\n");
        return 1;
    }

    // 创建 SSL 对象和 BIO 对象，并连接到服务器
    SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
    SSL *ssl = SSL_new(ctx);
    BIO *bio = BIO_new_ssl_connect(ctx);
    BIO_set_conn_hostname(bio, "www.baidu.com:443");
    BIO_get_ssl(bio, &ssl);
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
    if (BIO_do_connect(bio) <= 0) {
        perror("BIO_do_connect");
        exit(EXIT_FAILURE);
    }

    // 发送 HTTP 请求
    const char *request = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n";
    int request_len = strlen(request);
    if (BIO_write(bio, request, request_len) != request_len) {
        perror("BIO_write");
        exit(EXIT_FAILURE);
    }

    // 接收服务器响应并打印
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    int len;
    while ((len = BIO_read(bio, buf, sizeof(buf) - 1)) > 0) {
        buf[len] = '\0';
        printf("%s", buf);
    }
    if (len < 0) {
        perror("BIO_read");
        exit(EXIT_FAILURE);
    }

    // 清理资源
    BIO_free_all(bio);
    SSL_CTX_free(ctx);
    close(sockfd);

    return 0;
}
/*
 BSD socket+openssl接口访问HTPPS
 */
- (int) requestHTTPSWithSSL {
    // 初始化OpenSSL库
    SSL_library_init();
    SSL_load_error_strings();

    // 创建SSL_CTX对象
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
    if (ctx == NULL) {
        fprintf(stderr, "Failed to create SSL_CTX\n");
        return 1;
    }

    // 设置SSL_CTX选项
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION);

    // 获取服务器地址信息
    struct addrinfo hints, *result;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    getaddrinfo("www.baidu.com", "443", &hints, &result);

    // 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd == -1) {
        fprintf(stderr, "Failed to create socket\n");
        return 1;
    }
    // 连接服务器
    if (connect(sockfd, result->ai_addr, result->ai_addrlen) == -1) {
        fprintf(stderr, "Failed to connect to server\n");
        return 1;
    }

    // 创建SSL对象
    SSL *ssl = SSL_new(ctx);
    if (ssl == NULL) {
        fprintf(stderr, "Failed to create SSL\n");
        return 1;
    }
    SSL_set_fd(ssl, sockfd);

    // SSL握手
    if (SSL_connect(ssl) == -1) {
        fprintf(stderr, "Failed to perform SSL handshake\n");
        return 1;
    }

    printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
    X509 *server_cert = SSL_get_peer_certificate (ssl);
    printf ("Server certificate:\n");
    char* str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
    printf ("\t subject: %s\n", str);
    OPENSSL_free (str);
    str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
    printf ("\t issuer: %s\n", str);
    OPENSSL_free (str);
    X509_free (server_cert);
    
    // 发送HTTP请求
    char request[1024] = {0};
    sprintf(request, "GET https://www.baidu.com/ HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n");
    SSL_write(ssl, request, strlen(request));

    // 接收HTTP响应
    char response[4096]={0};
    while (SSL_read(ssl, response, sizeof(response))>0)        //通过SSL链接接收数据
    {
        printf("%s\n", response);
        memset(response, 0, 4096);
    }

    // 关闭SSL和socket连接
    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(sockfd);

    // 释放SSL_CTX对象
    SSL_CTX_free(ctx);

    return 0;
}
/*
 OC接口NSURLSession对系统代理的处理
 */
- (void)requestHTTPSWithNSURLSession
{
    NSString* strTargetURL = @"https://www.baidu.com"; // 想要访问的网址
    NSURLSessionConfiguration* confi = [NSURLSessionConfiguration defaultSessionConfiguration];
    // 创建一个代理服务器，包括HTTP或HTTPS代理，当然还可以添加SOCKS,FTP,RTSP等
    NSDictionary *proxyDict = @{
        (NSString *)kCFNetworkProxiesHTTPEnable:[NSNumber numberWithInt:1],
        (NSString *)kCFStreamPropertyHTTPProxyHost:@"127.0.0.1",
        (NSString *)kCFStreamPropertyHTTPProxyPort:@8000,
        (NSString *)kCFNetworkProxiesHTTPSEnable : [NSNumber numberWithInt:1],
        (NSString *)kCFStreamPropertyHTTPSProxyHost:@"127.0.0.1",
        (NSString *)kCFStreamPropertyHTTPSProxyPort:@8000,
    };
    confi.connectionProxyDictionary = @{}; // 覆盖系统代理，如果为@{}则强制忽律任何代理设置，不走代理。注掉即可使用系统设置的代理
    NSURLSession* session = [NSURLSession sessionWithConfiguration:confi delegate:self delegateQueue:nil];
    NSURLSessionTask* task = [session dataTaskWithURL:[NSURL URLWithString:strTargetURL] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"finish %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        [data writeToFile:@"/tmp/baidu.html" atomically:YES];
    }];
    [task resume];
}

/*
 直连HTTP网址，不经过任何代理，即使配置了系统代理也无效
 */
- (int) socketConnectServerDirect
{
    int sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sServer == -1) {
        perror("创建socket失败");
        return -1;
    }
    
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(TARGET_PORT);
    serverAddr.sin_addr.s_addr = inet_addr(TARGET_ADDRESS);
    
    
    if (connect(sServer, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        perror("连接服务器失败");
        close(sServer);
        return -1;
    }
    
    // 构造要发送给服务器的请求数据
    char request[1024] = {0};
    snprintf(request, sizeof(request), "GET / HTTP/1.1\r\nHost: %s:%d\r\n\r\n", TARGET_ADDRESS, TARGET_PORT);
    
    if (send(sServer, request, strlen(request), 0) < 0) {
        perror("向代理服务器发送请求失败");
        close(sServer);
        return -1;
    }
    
    // 接收服务器返回的数据
    char response[4096]={0};
    memset(response, 0, sizeof(response));
    if (recv(sServer, response, sizeof(response), 0) < 0) {
        perror("接收服务器返回数据失败");
        close(sServer);
        return -1;
    }
    printf("从服务器收到的响应：\n%s\n", response);
    return 0;
}
/*
 经过代理访问
 1.获取代理：来源--环境变量、参数、系统配置
 2.手撸代理协议过程
 */
/*
 BSD socket 通过代理访问HTTP网址
 */
- (int) socketConnectServerByProxyWithHTTP
{
    // 创建代理服务器的 Socket 连接
    int proxySocket = socket(AF_INET, SOCK_STREAM, 0);
    if (proxySocket == -1) {
        perror("代理服务器 Socket 创建失败");
        return -1;
    }
    
    // 设置代理服务器的地址和端口
    struct sockaddr_in proxyAddr;
    proxyAddr.sin_family = AF_INET;
    proxyAddr.sin_port = htons(PROXY_PORT);
    proxyAddr.sin_addr.s_addr = inet_addr(PROXY_ADDRESS);
    
    // 连接代理服务器
    if (connect(proxySocket, (struct sockaddr*)&proxyAddr, sizeof(proxyAddr)) < 0) {
        perror("连接代理服务器失败");
        close(proxySocket);
        return -1;
    }
    
    // 发送代理认证信息（如果需要认证）
    
    // 构造要发送给代理服务器的请求数据 http协议 代理
    char request[1024];
    snprintf(request, sizeof(request),  "GET http://%s:%d/ HTTP/1.1\r\n"
                                         "Host: %s:%d\r\n"
                                         "Connection: close\r\n"
                                         "Proxy-Connection: close\r\n\r\n",
                                         TARGET_ADDRESS, TARGET_PORT,
                                         TARGET_ADDRESS, TARGET_PORT);
    
    // 向代理服务器发送请求
    if (send(proxySocket, request, strlen(request), 0) < 0) {
        perror("向代理服务器发送请求失败");
        close(proxySocket);
        return -1;
    }
    
    // 接收代理服务器返回的数据
    char response[4096] = {0};
    memset(response, 0, sizeof(response));
    if (recv(proxySocket, response, sizeof(response), 0) < 0) {
        perror("接收代理服务器返回数据失败");
        close(proxySocket);
        return -1;
    }
    printf("从代理服务器收到的响应：\n%s\n", response);
    return 0;
}
/*
 BSD socket+openssl接口通过代理访问HTPPS
 */
- (int) socketConnectServerByProxyWithHTTPS
{
    // 创建代理服务器的 Socket 连接
    int proxySocket = socket(AF_INET, SOCK_STREAM, 0);
    if (proxySocket == -1) {
        perror("代理服务器 Socket 创建失败");
        return -1;
    }
    struct timeval timeout = {10, 0}; // 分别是秒和微秒
     // 发送超时
    //setsockopt(socket，SOL_SOCKET,SO_SNDTIMEO，(char *)&timeout,sizeof(struct timeval));
    // 接收超时
    setsockopt(proxySocket,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));

    
    // 设置代理服务器的地址和端口
    struct sockaddr_in proxyAddr;
    proxyAddr.sin_family = AF_INET;
    proxyAddr.sin_port = htons(8000);  // 代理服务器端口
    proxyAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  // 代理服务器地址

    // 连接代理服务器
    if (connect(proxySocket, (struct sockaddr*)&proxyAddr, sizeof(proxyAddr)) < 0) {
        perror("连接代理服务器失败");
        close(proxySocket);
        return -1;
    }
    
    // 构造要发送给代理服务器的 CONNECT 请求数据
    const char* request = "CONNECT www.baidu.com:443 HTTP/1.1\r\nHost: www.baidu.com\r\nProxy-Connection: Keep-Alive\r\n\r\n";
    
    // 向代理服务器发送 CONNECT 请求
    if (send(proxySocket, request, strlen(request), 0) < 0) {
        perror("向代理服务器发送 CONNECT 请求失败");
        close(proxySocket);
        return -1;
    }
    {
        // 接收代理服务器的 CONNECT 响应
        char response[4096];
        memset(response, 0, sizeof(response));
        if (recv(proxySocket, response, sizeof(response), 0) < 0) {
            perror("接收代理服务器返回数据失败");
            close(proxySocket);
            return -1;
        }
        printf("从代理服务器收到的 CONNECT 响应：\n%s\n", response);
    }
    SSL_library_init();
    SSL_load_error_strings();

    // 创建SSL_CTX对象
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
    if (ctx == NULL) {
        fprintf(stderr, "Failed to create SSL_CTX\n");
        return 1;
    }

    // 设置SSL_CTX选项
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION);

    // 创建SSL对象
    SSL *ssl = SSL_new(ctx);
    if (ssl == NULL) {
        fprintf(stderr, "Failed to create SSL\n");
        return 1;
    }
    SSL_set_fd(ssl, proxySocket);

    // SSL握手
    if (SSL_connect(ssl) == -1) {
        fprintf(stderr, "Failed to perform SSL handshake\n");
        return 1;
    }

    printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
    X509             *server_cert;
    server_cert = SSL_get_peer_certificate (ssl);
    printf ("Server certificate:\n");
    char* str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
    printf ("\t subject: %s\n", str);
    OPENSSL_free (str);
    str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
    printf ("\t issuer: %s\n", str);
    OPENSSL_free (str);
    X509_free (server_cert);

    
    // 发送HTTP请求
    char request1[1024] = {0};
    sprintf(request1, "GET https://www.baidu.com/ HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n");
    SSL_write(ssl, request1, strlen(request1));

    // 接收HTTP响应
    char response[4096]={0};
    while (SSL_read(ssl, response, sizeof(response))>0)        //通过SSL链接接收数据
    {
        printf("%s\n", response);
        memset(response, 0, 4096);
    }

    
    // 关闭SSL和socket连接
    SSL_shutdown(ssl);
    SSL_free(ssl);

    // 释放SSL_CTX对象
    SSL_CTX_free(ctx);

    // 关闭代理服务器的 Socket 连接
    close(proxySocket);

    return 0;
}

- (void)findSysProxyByAuto
{
    //http://127.0.0.1:11085/pac/proxy.js
    NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession* ses = [NSURLSession sharedSession];
    NSURLSessionTask*task = [ses dataTaskWithURL:[NSURL URLWithString:@"http://127.0.0.1:11085/pac/proxy.js"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        //[self sss:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
        [self performSelectorOnMainThread:@selector(sss:) withObject:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] waitUntilDone:NO];
    }];
    [task resume];
}
- (void)sss:(NSString*)strProxy
{
    
    JSGlobalContextRef context = JSGlobalContextCreate(NULL);
    JSStringRef script = JSStringCreateWithUTF8CString(strProxy.UTF8String);
    JSEvaluateScript(context, script, NULL, NULL, 1, NULL);
    JSStringRelease(script);

    JSStringRef function = JSStringCreateWithUTF8CString("FindProxyForURL");
    JSObjectRef globalObject = JSContextGetGlobalObject(context);
    JSValueRef value = JSEvaluateScript(context, function, globalObject, NULL, 1, NULL);
    JSStringRelease(function);

    JSObjectRef functionObject = JSValueToObject(context, value, NULL);
    JSValueRef arguments[2];
    arguments[0] = JSStringCreateWithUTF8CString("http://www.baidu.com");
    arguments[1] = JSStringCreateWithUTF8CString("www.baidu.com");
    JSValueRef result = JSObjectCallAsFunction(context, functionObject, NULL, 2, arguments, NULL);

    JSStringRef resultString = JSValueToStringCopy(context, result, NULL);
    size_t bufferSize = JSStringGetMaximumUTF8CStringSize(resultString);
    char* buffer = (char*)malloc(bufferSize);
    JSStringGetUTF8CString(resultString, buffer, bufferSize);
    
    printf("Proxy: %s\n", buffer);

    JSGlobalContextRelease(context);
    free(buffer);
}
- (void)findSysProxyByDefault{
    CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();
    if (proxySettings) {
        CFStringRef proxyHost = CFDictionaryGetValue(proxySettings, kCFNetworkProxiesHTTPProxy);
        CFNumberRef proxyPort = CFDictionaryGetValue(proxySettings, kCFNetworkProxiesHTTPPort);

        if (proxyHost && proxyPort) {
            CFStringRef urlStr = CFStringCreateWithCString(NULL, "http://www.baidu.com", kCFStringEncodingUTF8);
            CFURLRef url = CFURLCreateWithString(NULL, urlStr, NULL);

            CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), url, kCFHTTPVersion1_1);
            CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(NULL, request);

            const void *keys[] = {(void *)kCFStreamPropertyHTTPProxyHost, (void *)kCFStreamPropertyHTTPProxyPort};
            const void *values[] = {(void *)proxyHost, (void *)proxyPort};
            // 设置代理
            CFDictionaryRef proxyDict = CFDictionaryCreate(NULL,
                                                           keys,
                                                           values,
                                                           1,
                                                           &kCFTypeDictionaryKeyCallBacks,
                                                           &kCFTypeDictionaryValueCallBacks);
            CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);

            // 发起请求
            CFReadStreamOpen(readStream);
            CFIndex bytesRead;
            UInt8 buffer[4096];
            while ((bytesRead = CFReadStreamRead(readStream, buffer, sizeof(buffer))) > 0) {
                fwrite(buffer, 1, bytesRead, stdout);
            }

            // 清理资源
            CFRelease(urlStr);
            CFRelease(url);
            CFRelease(request);
            CFRelease(readStream);
            CFRelease(proxyDict);
        }

        CFRelease(proxySettings);
    }
    // Do any additional setup after loading the view.
}
- (IBAction)onclick:(id)sender {
    [self test];
    
}
- (int) test
{
    struct sockaddr_in server_addr;
    struct hostent *server;
    int sockfd, portno = 80;

    char buffer[1024];
    const char *host = "www.baidu.com";
    const char *request = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n";

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0) {
        perror("Error opening socket");
        exit(1);
    }

    // 获取服务器信息
    server = gethostbyname(host);
    if (server == NULL) {
        fprintf(stderr,"Error, no such host\n");
        exit(0);
    }

    // 设置服务器地址结构
    bzero((char *) &server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, (char *)&server_addr.sin_addr.s_addr, server->h_length);
    server_addr.sin_port = htons(portno);

    // 连接服务器
    if (connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        perror("Error connecting");
        exit(1);
    }

    // 发送 HTTP 请求
    if (write(sockfd, request, strlen(request)) < 0) {
        perror("Error writing to socket");
        exit(1);
    }

    // 读取并打印服务器响应
    bzero(buffer, 1024);
    int icnt = 0;
    while(true)
    {
        printf("cnt:%d\n",icnt);
        int iret = read(sockfd, buffer, 1023);
        printf("cnt:%d ret:%d\n",icnt++,iret);
        if (iret <= 0)
            break;
    }

    close(sockfd);
    printf("finish\n");
    return 0;
}


@end

